package com.sky.aspect;

import com.sky.annotation.AutoFill;
import com.sky.constant.AutoFillConstant;
import com.sky.context.BaseContext;
import com.sky.enumeration.OperationType;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.time.LocalDateTime;

/**
 *  自定义切面类， 实现公共字段的自动填充的处理逻辑
 */

@Aspect
@Component
@Slf4j
public class AutoFillAspect {

    /**
     *  指定切入点 ： 就是对哪些类的哪些方法进行拦截
     */
    @Pointcut("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)") // 拦截到 mapper 下面的所有方法 和 所有的类
    // 但是我们并不期望所有的方法都进行拦截，只希望对公共字段操作又影响的方法进行拦截
    // 也就是  @annotation(com.sky.annotation.AutoFill) 这一段
    // 表示同时也要满足要拦截的这个方法上加入 了 这个注解
    public void autoFillPointCut(){}

    // 定义一个通知
    // 为的就是 为这些公共的字段进行统一赋值
    // 这里用的是 前置通知， 因为需要在执行 INSERT , UPDATE方法之前 就需要对这些字段进行赋值
    @Before("autoFillPointCut()") // 前置通知
    public void autoFill(JoinPoint joinPoint) throws InvocationTargetException, IllegalAccessException { // 需要传入参数， 也就是连接点
        log.info("开始进行公共字段的自动填充..");

        // 第一步 需要获取到当前方法上拦截的数据库类型
        MethodSignature signature = (MethodSignature)joinPoint.getSignature(); // 方法签名对象

        AutoFill autoFill = signature.getMethod().getAnnotation(AutoFill.class);// 获得方法上的注解对象

        OperationType operationType = autoFill.value(); // 获得数据库的操作类型
        // 这都是反射包里面的代码
        // 通过这几句代码是为了获得 mapper 里面的   @AutoFill(value = OperationType.UPDATE) 这一部分的操作类型

        // 获取到当前被拦截的方法的参数-- 实体对象
        // 通过连接点获取参数 , 只要是通过 AutoFill 注解进行修饰的代码的参数 都可以通过这样来获得
        Object[] args = joinPoint.getArgs();// 这个方法是获取所有的参数， insert 方法 或者 update 方法
        // 只要加了 AutoFill 这个注解 就是对应这个参数
        // 接下来做一个约定
        // 如果想实现自动填充的话， 一定要保证我们的实体放在第一个位置
        // 因为 接下来要获取的话 就获取第一个就可以了
        if(args == null || args.length == 0){
            // 判断这个方法是否为 空
            return;
        }

        // 取参数
        Object entity = args[0]; // 实体类放在的是第一个 所以 用 0 下标 而且用 Object 来接收
        // 因为 在 不一样的分类当中 获取的是不一样的实体， 所以就用 Object 来统一

        // 为公共属性 进行赋值， 准备赋值的数据
        // 需要赋值的数据 一共 有两个： 一个是时间  一个是当前用户登录的id
        LocalDateTime now = LocalDateTime.now(); // 获取时间
        Long currentId = BaseContext.getCurrentId();//获取当前用户定义的 id


        // 根据当前不同的操作类型，为对应的属性通过反射来赋值
        if(operationType == OperationType.INSERT){
            // 如果是插入操作
            // 那就需要为 四个 公共字段共同赋值
            try {
                // 获得里面的 set 方法
                Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);// 这样就可以获得一个方法
                Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
                Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
                Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);

                // 通过反射为对象进行赋值
                setCreateTime.invoke(entity,now);
                setCreateUser.invoke(entity,currentId);
                setUpdateTime.invoke(entity,now);
                setUpdateUser.invoke(entity,currentId); // 通过实体调用 并进行赋值

            } catch (NoSuchMethodException e) {

            }
        } else if (operationType == OperationType.UPDATE) {
            // 如果是 更新操作
            // 那就需要为 两个 字段进行赋值 就可以了
            // upDateTime 和 upDateUser
            try {
                // 获得里面的 set 方法
                Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
                Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);

                // 通过反射为对象进行赋值
                setUpdateTime.invoke(entity,now);
                setUpdateUser.invoke(entity,currentId); // 通过实体调用 并进行赋值

            } catch (NoSuchMethodException e) {

            }

        }

    }
}
